#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <getch.h>
#include <time.h>
#include <utime.h>

// 地址类型重定义
typedef struct sockaddr* SAP;
// 缓冲区大小
#define BUF_SIZE (1024)
// 缓冲区
char sbuf[BUF_SIZE],rbuf[BUF_SIZE],cmd[BUF_SIZE];
// 命令通道和数据通道socket描述符
int cmd_fd,data_fd;
// 本地文件、服务端文件的最后修改时间
char l_time[15],s_time[15];
// 是否上传、下传的状态标志
bool is_put , is_get;

// 接收状态码并检查
int recv_check_ftp(int status,bool is_exit)
{
	bzero(rbuf,BUF_SIZE);
	int ret = recv(cmd_fd,rbuf,BUF_SIZE,0);
	if(0 >= ret)
	{
		perror("recv");
		exit(EXIT_FAILURE);
	}

	printf("size:%2d recv：%s",ret,rbuf);
	sscanf(rbuf,"%d",&ret);
	if(ret == status)
		return EXIT_SUCCESS;
	else if(is_exit)
		exit(EXIT_FAILURE);
	else	
		return EXIT_FAILURE;
}

// 向服务端发送命令
void send_cmd_ftp(void)
{
	int ret = send(cmd_fd,sbuf,strlen(sbuf),0);
	if(0 > ret)
	{
		perror("send");
		exit(EXIT_FAILURE);
	}
	printf("size:%2d send：%s",ret,sbuf);
}

// 连接服务端
int connect_ftp(const char* ip,short port)
{
	int fd = socket(AF_INET,SOCK_STREAM,0);
	if(EXIT_SUCCESS > cmd_fd)
	{
		perror("socket");
		return EXIT_FAILURE;
	}

	struct sockaddr_in addr = {};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip);

	if(connect(fd,(SAP)&addr,sizeof(addr)))
	{
		perror("connect");
		exit(EXIT_FAILURE);
	}

	return fd;
}

// 登录服务端
void login_ftp(void)
{
	char tmp[255] = {};
	printf("please input user:");
	gets(tmp);
	sprintf(sbuf,"USER %s\n",tmp);
	send_cmd_ftp();
	recv_check_ftp(331,false);

	printf("please input pass:");
	gets(tmp);
	sprintf(sbuf,"PASS %s\n",tmp);
	send_cmd_ftp();
	recv_check_ftp(230,false);
}

// 开启数据通道
void pasv_ftp(void)
{
	sprintf(sbuf,"PASV\n");
	send_cmd_ftp();
	if(recv_check_ftp(227,false))
	{
		puts("服务器不支持PASV模式，其它模式还未实现，敬请期待!");
		exit(EXIT_FAILURE);
	}

	int ip1,ip2,ip3,ip4,port1,port2;
	sscanf(rbuf,"227 Entering Passive Mode (%d,%d,%d,%d,%d,%d",
			&ip1,&ip2,&ip3,&ip4,&port1,&port2);
			
	char ip[16] = {};
	sprintf(ip,"%d.%d.%d.%d",ip1,ip2,ip3,ip4);
	
	data_fd = connect_ftp(ip,port1*256+port2);
}


// 传输数据
void transfer_data(int rfd,int wfd)
{
	int ret = 0;
	while(ret = read(rfd,sbuf,BUF_SIZE))
	{	
		write(wfd,sbuf,ret);
	}
	close(rfd);
}

// 获取当前目录详细信息
void list_ftp(void)
{
	pasv_ftp();
	sprintf(sbuf,"LIST -al\n");
	send_cmd_ftp();
	recv_check_ftp(150,true);
	transfer_data(data_fd,STDOUT_FILENO);
	recv_check_ftp(226,true);
}

// 上传文件的最后修改时间
void send_mtime_ftp(void)
{
	close(data_fd);
	recv_check_ftp(226,true);
	sprintf(sbuf,"MDTM %s %s\n",l_time,cmd+4);
	send_cmd_ftp();
	recv_check_ftp(213,false);
}

void recv_mtime_ftp(void)
{
	sprintf(sbuf,"MDTM %s\n",cmd+4);
	send_cmd_ftp();
	recv_check_ftp(213,false);

	sscanf(rbuf,"%*d %s",s_time);
	s_time[14] = '\0';
}

int64_t recv_size_ftp(void)
{
	sprintf(sbuf,"SIZE %s\n",cmd+4);
	send_cmd_ftp();
	if(recv_check_ftp(213,false))
		return -1;

	int64_t size;
	sscanf(rbuf,"%*d %lld",&size);
	return size;
}

void read_mtime(void)
{
	struct stat buf;
	stat(cmd+4,&buf);
	struct tm* tm = localtime(&buf.st_mtime);
 	sprintf(l_time,"%d%d%d%d%d%d",tm->tm_year+1900,tm->tm_mon+1,tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec);
}

void write_mtime(void)
{
	close(data_fd);
	struct tm tm;
	sscanf(s_time,"%4d%2d%2d%2d%2d%2d",&tm.tm_year,&tm.tm_mon,&tm.tm_mday,&tm.tm_hour,&tm.tm_min,&tm.tm_sec);
	tm.tm_year -= 1900;
	tm.tm_mon -= 1;
	mktime(&tm);

	struct utimbuf utimebuf;
	utimebuf.actime = mktime(&tm);
	utimebuf.modtime = mktime(&tm);
	utime(cmd+4,&utimebuf);
}

// 下载文件
void get_file_ftp(const char* file)
{
	// 开启二进制模式
	sprintf(sbuf,"TYPE I\n");
	send_cmd_ftp();
	recv_check_ftp(200,false);

	// 获取服务端文件的最后修改时间
	recv_mtime_ftp();
	// 读取本地文件的最后修改时间
	read_mtime();
	
	// 开启数据通道
	pasv_ftp();

	// 标记下载状态
	is_get = true;
	int fd = open(file,O_WRONLY|O_CREAT,0644);
	if(0 > fd)
	{
		perror("open");
		return;
	}
	
	// 如果文件已经存在则会获取文件的字节数并把位置指针调整到文件末尾
	uint64_t fsize = lseek(fd,0,SEEK_END);
	// 如果是同一份文件则自动从断点处下载
	if(0 == strcmp(s_time,l_time))
	{
		sprintf(sbuf,"REST %lld\n",fsize);
		send_cmd_ftp();
		if(recv_check_ftp(350,false))
			puts("服务器不支持断点续传!");
	}

	sprintf(sbuf,"RETR %s\n",file);
	send_cmd_ftp();
	recv_check_ftp(150,true);

	transfer_data(data_fd,fd);
	write_mtime();
	is_get = false;
	recv_check_ftp(226,true);
}

void put_file_ftp(const char* file)
{
	// 开启二进制模式
	sprintf(sbuf,"TYPE I\n");
	send_cmd_ftp();
	recv_check_ftp(200,false);

	// 获取服务端文件的最后修改时间
	recv_mtime_ftp();
	// 读取本地文件的最后修改时间
	read_mtime();
	
	// 开启数据通道
	pasv_ftp();
	
	// 标记下载状态
	is_put = true;
	
	// 以只读方式打开本地文件
	int fd = open(file,O_RDONLY);
	if(0 > fd)
	{
		perror("open");
		return;
	}

	// 判断本地文件和服务端文件是否是同一份
	if(0 == strcmp(s_time,l_time))
	{
		// 获取服务端文件的大小
		int64_t fsize = recv_size_ftp();
		// 设置本地文件的位置指针，传输数据时会从此位置开始上传
		lseek(fd,fsize,SEEK_SET);
		// 告诉服务端从指定位置接收数据
		sprintf(sbuf,"REST %lld\n",fsize);
		send_cmd_ftp();
		if(recv_check_ftp(350,false))
			puts("服务器不支持断点续传!");
	}

	sprintf(sbuf,"STOR %s\n",file);
	send_cmd_ftp();
	recv_check_ftp(150,true);
	
	transfer_data(fd,data_fd);
	send_mtime_ftp();
	is_put = false;
}

void sigint(int signum)
{
	if(is_put)
		send_mtime_ftp();
	if(is_get)
		write_mtime();
	exit(0);
}

int main(int argc,char* const argv[])
{
	signal(SIGINT,sigint);
	if(2 == argc)
		cmd_fd = connect_ftp(argv[1],21);
	else if(3 == argc)
		cmd_fd = connect_ftp(argv[1],atoi(argv[2]));
	else
	{
		puts("User：ftp xxx.xxx.xxx.xxx [port]");
		return EXIT_SUCCESS;
	}

	recv_check_ftp(220,true);

	login_ftp();

	while(true)
	{
		printf("ftp>");
		gets(cmd);

		if(0 == strcmp("user",cmd))
			login_ftp();
		if(0 == strcmp("ls",cmd))
			list_ftp();
		if(0 == strncmp("get ",cmd,4))
			get_file_ftp(cmd+4);
		if(0 == strncmp("put ",cmd,4))
			put_file_ftp(cmd+4);
		/*
		if(0 == strcmp("cd",buf))
		if(0 == strcmp("mkdir",buf))
		if(0 == strcmp("rmdir",buf))
		if(0 == strcmp("mv",buf))
		if(0 == strcmp("dele",buf))
		if(0 == strcmp("put",buf))
		*/
	}
}



